package edu.jxufe.nb112.common.security.shiro;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Created by lvxi on 2017/11/20.
 * Note:
 */
public class ShiroRedisCache <K, V> implements Cache<K, V> {

    private static final Logger LOG = LoggerFactory.getLogger(ShiroRedisCache.class.getName());

    private RedisCache redisCache;
    /**
     * 键前缀 ，缓存中的key前缀由keyPrefix+':'+cacheName+':'
     */
    private String keyPrefix;

    public ShiroRedisCache(String name,String prefix,RedisTemplate redisTemplate,long expiration){
        RedisSerializer serializer = new StringRedisSerializer();
        keyPrefix = prefix+':'+name+':';
        this.redisCache = new RedisCache(name,serializer.serialize(keyPrefix),redisTemplate,expiration);
    }

    public V get(K key) throws CacheException {
        try {
            if (key == null) {
                return null;
            }else{
                if (LOG.isDebugEnabled()) {
                    LOG.debug("ShiroRedisCache获取缓存key={}", key);
                }
                ValueWrapper valueWrapper = redisCache.get(key);
                if(valueWrapper==null) {
                    return null;
                }
                V value = (V) valueWrapper.get();
                return value;
            }
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    public V put(K key, V value) throws CacheException {
        try {
            if (LOG.isDebugEnabled()) {
                LOG.debug("ShiroRedisCache存储缓存key={}",key);
            }
            redisCache.put(key,value);
            return value;
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    public V remove(K key) throws CacheException {
        try {
            if (LOG.isDebugEnabled()) {
                LOG.debug("ShiroRedisCache删除缓存key={}", key);
            }
            //返回被删除的值
            V previous = get(key);
            redisCache.evict(key);
            return previous;
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    public void clear() throws CacheException {

        try {
            if (LOG.isDebugEnabled()) {
                LOG.debug("ShiroRedisCache删除所有{}缓存",redisCache.getName());
            }
            redisCache.clear();
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    /**
     * redis中所有缓存数量，可能包括其他缓存
     * @return
     */
    public int size() {

        if (LOG.isDebugEnabled()) {
            LOG.debug("ShiroRedisCache获取当前redis所有缓存数量");
        }
        final RedisTemplate<String, V> redisTemplate = (RedisTemplate<String, V>)redisCache.getNativeCache();
        return redisTemplate.execute(new RedisCallback<Integer>() {
            public Integer doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer redisSerializer = redisTemplate.getKeySerializer();
                Set<byte[]> keys = connection.keys(redisSerializer.serialize(keyPrefix+"*"));
                return keys.size();
            }
        });
    }

    public Set<K> keys() {
        if (LOG.isDebugEnabled()) {
            LOG.debug("ShiroRedisCache获取所有主键keys");
        }
        final RedisTemplate<String, V> redisTemplate = (RedisTemplate<String, V>)redisCache.getNativeCache();
        return redisTemplate.execute(new RedisCallback<Set<K>>() {
            public Set<K> doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer redisSerializer = redisTemplate.getKeySerializer();
                Set<byte[]> keys = connection.keys(redisSerializer.serialize(keyPrefix+"*"));
                Set<K> cacheKeys = new LinkedHashSet<K>();
                if (!org.springframework.util.CollectionUtils.isEmpty(keys)) {
                    for (byte[] key : keys) {
                        K keyStr = ((RedisSerializer<K>)redisTemplate.getKeySerializer()).deserialize(key);
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("key={}",keyStr);
                        }
                        cacheKeys.add(keyStr);
                    }
                }
                return cacheKeys;
            }
        });
    }

    public Collection<V> values() {
        if (LOG.isDebugEnabled()) {
            LOG.debug("ShiroRedisCache获取所有值values");
        }
        final RedisTemplate<String, V> redisTemplate = (RedisTemplate<String, V>)redisCache.getNativeCache();
        return redisTemplate.execute(new RedisCallback<Collection<V>>() {
            public Collection<V> doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer redisKeySerializer = redisTemplate.getKeySerializer();
                RedisSerializer<V> redisValueSerializer = (RedisSerializer<V>) redisTemplate.getValueSerializer();
                Set<byte[]> keys = connection.keys(redisKeySerializer.serialize(keyPrefix+"*"));
                Collection<V> cacheValues= new ArrayList<V>();
                if (!org.springframework.util.CollectionUtils.isEmpty(keys)) {
                    for (byte[] key : keys) {
                        byte[] valueByte = connection.get(key);
                        if(valueByte!=null){
                          Object value =  redisValueSerializer.deserialize(valueByte);
                            cacheValues.add((V) value);
                        }
                    }
                }
                return cacheValues;
            }
        });
    }
}
